package com.atguigu.tingshu.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.account.client.UserAccountFeignClient;
import com.atguigu.tingshu.common.constant.RabbitConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.enums.order.OrderStatus;
import com.atguigu.tingshu.enums.order.PayWay;
import com.atguigu.tingshu.model.order.OrderDerate;
import com.atguigu.tingshu.model.order.OrderDetail;
import com.atguigu.tingshu.model.order.OrderInfo;
import com.atguigu.tingshu.order.helper.SignHelper;
import com.atguigu.tingshu.order.mapper.OrderInfoMapper;
import com.atguigu.tingshu.order.service.OrderInfoService;
import com.atguigu.tingshu.order.template.OrderConfirmClient;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.atguigu.tingshu.vo.order.OrderDerateVo;
import com.atguigu.tingshu.vo.order.OrderDetailVo;
import com.atguigu.tingshu.vo.order.OrderInfoVo;
import com.atguigu.tingshu.vo.order.TradeVo;
import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private OrderConfirmClient orderConfirmClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserAccountFeignClient userAccountFeignClient;

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public OrderInfoVo trade(TradeVo vo) {
        return orderConfirmClient.confirm(vo);
    }

    @Override
    public void submitOrder(OrderInfoVo orderInfoVo) {
        // 验签
        Map<String, Object> map = JSON.parseObject(JSON.toJSONString(orderInfoVo), HashMap.class);
        // 为了保证验签通过，这里需要和购买时一致。（提交订单时，选择的支付方式可能和购买时不一致）
        map.put("payWay", SystemConstant.ORDER_PAY_WAY_WEIXIN);
        SignHelper.checkSign(map);
        // 幂等性校验
        String tradeNo = orderInfoVo.getTradeNo();
        if (StringUtils.isEmpty(tradeNo)) {
            throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
        }
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                "then " +
                "    return redis.call('del', KEYS[1]) " +
                "else " +
                "    return 0 " +
                "end";
        Boolean flag = (Boolean) redisTemplate.execute(new DefaultRedisScript(script, Boolean.TYPE),
                Arrays.asList(RedisConstant.USER_TRADE_PREFIX + tradeNo),
                tradeNo);
        if (!flag) throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
        // TODO: 判断是否重复购买UserPaidAlbum UserPaidTrack
        // 判断支付方式
        if (SystemConstant.ORDER_PAY_WAY_WEIXIN.equals(orderInfoVo.getPayWay())) {
            // 微信支付方式，先保存订单信息
            this.saveOrder(orderInfoVo);
        } else if (SystemConstant.ORDER_PAY_WAY_ACCOUNT.equals(orderInfoVo.getPayWay())) {
            // 余额支付，先查询并扣减余额，然后保存订单
            AccountLockVo accountLockVo = AccountLockVo.builder()
                    .orderNo(tradeNo)
                    .userId(AuthContextHolder.getUserId())
                    .amount(orderInfoVo.getOrderAmount())
                    .content("锁定余额")
                    .build();
            Result<AccountLockResultVo> resultVoResult = userAccountFeignClient.checkAndLockAccount(accountLockVo);
            if (!resultVoResult.getCode().equals(ResultCodeEnum.SUCCESS.getCode())) {
                // 如果响应结果不是200，则说明验余额锁余额失败
                throw new GuiguException(resultVoResult.getCode(), resultVoResult.getMessage());
            }
            try {
                // 保存订单
                this.saveOrder(orderInfoVo);
                // 发送mq消息，扣减余额
                rabbitTemplate.convertAndSend(RabbitConstant.ORDER_EXCHANGE, RabbitConstant.REDUCE_ACCOUNT_RK, tradeNo);
            } catch (GuiguException e) {
                // 保存订单异常，发送mq消息，释放锁定金额
                rabbitTemplate.convertAndSend(RabbitConstant.ORDER_EXCHANGE, RabbitConstant.RELEASE_ACCOUNT_RK, tradeNo);
                e.printStackTrace();
            }

        }
    }

    @Override
    public void updateOrderStatus(String orderNo) {
        // 根据订单号查询订单信息
        OrderInfo orderInfo = this.lambdaQuery().eq(OrderInfo::getOrderNo, orderNo).one();
        if (orderInfo == null) throw new GuiguException(ResultCodeEnum.DATA_ERROR);
        // 更新订单的支付状态
        this.lambdaUpdate()
                .eq(OrderInfo::getOrderNo, orderNo)
                .eq(OrderInfo::getOrderStatus, SystemConstant.ORDER_STATUS_UNPAID)
                .set(OrderInfo::getOrderStatus, SystemConstant.ORDER_STATUS_PAID)
                .update();
        // 发起远程调用，处理用户的订单购买内容
        List<OrderDetail> orderDetails = Db.lambdaQuery(OrderDetail.class).eq(OrderDetail::getOrderId, orderInfo.getId()).list();
        UserPaidRecordVo userPaidRecordVo = UserPaidRecordVo.builder()
                .userId(orderInfo.getUserId())
                .orderNo(orderNo)
                .itemType(orderInfo.getItemType())
                .itemIdList(orderDetails.stream().map(OrderDetail::getItemId).collect(Collectors.toList()))
                .build();
        userInfoFeignClient.makeOrderPaidInfo(userPaidRecordVo);
    }

    @Override
    public OrderInfo getOrderInfo(String orderNo) {
        OrderInfo orderInfo = this.lambdaQuery().eq(OrderInfo::getOrderNo, orderNo).one();
        setOrderStatusName(orderInfo);
        setPayWay(orderInfo);
        List<OrderDetail> orderDetails = Db.lambdaQuery(OrderDetail.class).eq(OrderDetail::getOrderId, orderInfo.getId()).list();
        orderInfo.setOrderDetailList(orderDetails);
        List<OrderDerate> orderDerates = Db.lambdaQuery(OrderDerate.class).eq(OrderDerate::getOrderId, orderInfo.getId()).list();
        orderInfo.setOrderDerateList(orderDerates);
        return orderInfo;
    }

    @Override
    public IPage<OrderInfo> findOrderPage(Integer pageNum, Integer pageSize) {
        IPage<OrderInfo> orderInfoPage = this.page(
                new Page<>(pageNum, pageSize),
                new LambdaQueryWrapper<OrderInfo>()
                        .eq(OrderInfo::getUserId, AuthContextHolder.getUserId())
                        .orderByDesc(OrderInfo::getId)
        );
        List<OrderInfo> orderInfoList = orderInfoPage.getRecords();
        if (CollUtil.isEmpty(orderInfoList)) return orderInfoPage;
        orderInfoList.forEach(item -> {
            setOrderStatusName(item);
            setPayWay(item);
            List<OrderDetail> orderDetails = Db.lambdaQuery(OrderDetail.class).eq(OrderDetail::getOrderId, item.getId()).list();
            item.setOrderDetailList(orderDetails);
        });
        return orderInfoPage;
    }

    /**
     * 保存订单信息
     *
     * @param orderInfoVo
     */
    private void saveOrder(OrderInfoVo orderInfoVo) {
        List<OrderDetailVo> orderDetailVoList = orderInfoVo.getOrderDetailVoList();
        if (CollectionUtils.isEmpty(orderDetailVoList)) {
            throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
        }
        // 保存order_info
        OrderInfo orderInfo = new OrderInfo();
        BeanUtils.copyProperties(orderInfoVo, orderInfo);
        orderInfo.setUserId(AuthContextHolder.getUserId());
        orderInfo.setOrderTitle(orderDetailVoList.get(0).getItemName());
        orderInfo.setOrderNo(orderInfoVo.getTradeNo());
        orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_UNPAID);
        this.save(orderInfo);
        Long orderId = orderInfo.getId();
        // 保存order_detail
        List<OrderDetail> orderDetails = orderDetailVoList.stream().map(item -> {
            OrderDetail orderDetail = BeanUtil.copyProperties(item, OrderDetail.class);
            orderDetail.setOrderId(orderId);
            return orderDetail;
        }).collect(Collectors.toList());
        Db.saveBatch(orderDetails);
        // 保存order_derate
        List<OrderDerateVo> orderDerateVoList = orderInfoVo.getOrderDerateVoList();
        if (CollUtil.isNotEmpty(orderDerateVoList)) {
            List<OrderDerate> orderDerates = orderDerateVoList.stream().map(item -> {
                OrderDerate orderDerate = BeanUtil.copyProperties(item, OrderDerate.class);
                orderDerate.setOrderId(orderId);
                return orderDerate;
            }).collect(Collectors.toList());
            Db.saveBatch(orderDerates);
        }
    }

    private void setOrderStatusName(OrderInfo orderInfo) {
        for (OrderStatus orderStatus : OrderStatus.values()) {
            if (orderStatus.getCode().equals(orderInfo.getOrderStatus())) {
                orderInfo.setOrderStatusName(orderStatus.getName());
            }
        }
    }

    private void setPayWay(OrderInfo orderInfo) {
        for (PayWay payWay : PayWay.values()) {
            if (payWay.getCode().equals(orderInfo.getPayWay())) {
                orderInfo.setPayWayName(payWay.getName());
            }
        }
    }
}
